home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Games of Daze
/
Infomagic - Games of Daze (Summer 1995) (Disc 1 of 2).iso
/
x2ftp
/
msdos
/
docs
/
mod2txt
/
chap12.txt
< prev
next >
Wrap
Text File
|
1987-03-25
|
26KB
|
586 lines
CHAPTER 12 - Pointers and Dynamic Allocation
PREREQUISITES FOR THIS MATERIAL
In order to understand this chapter, you should have a
good grasp of the entirety of Part I and a clear
understanding of chapter 11.
For certain types of programs, pointers and dynamic
allocation can be a tremendous advantage, but most programs
do not need such a high degree of data structure. For that
reason, it would probably be to your advantage to lightly
skim over these topics and come back to them later when you
have a substantial base of Modula-2 programming experience.
It would be good to at least skim over this material rather
than completely neglecting it, so you will have an idea of
how pointers and dynamic allocation work and that they are
available for your use when needed.
A complete understanding of this material will require
deep concentration as it is very complex and not at all
intuitive. Nevertheless, if you pay close attention, you
will have a good grasp of pointers and dynamic allocation in
a short time.
WHAT ARE POINTERS, AND WHAT GOOD ARE THEY?
If you examine POINTERS.MOD, you will see a very trivial
example of pointers and how they are used. In the VAR
declaration, you will see that the two variables have the
two reserved words POINTER TO in front of their respective
types. These are not actually variables, instead, they
point to dynamically allocated variables that have not been
defined yet, and they are called pointers. We will see,
when we get to chapter 14, that a pointer can be used to
point to any variable, even a statically defined one, but
that will have to wait awhile.
The pointer "MyName" is a pointer to a 20 character
string and is therefore not a variable into which a value
can be stored. This is a very special TYPE, and it cannot
be assigned a character string, only a pointer value or
address. The pointer actually points to an address
somewhere within the computer memory, and can access the
data stored at that address.
Your computer has some amount of memory installed in it.
If it is an IBM-PC or compatible, it can have up to 640K of
RAM which is addressable by various programs. The operating
system requires about 60K of the total, and your program can
use up to 64K assuming that your compiler uses a small
memory model. Adding those two numbers together results in
about 124K. Any memory you have installed in excess of that
Page 72
CHAPTER 12 - Pointers and Dynamic Allocation
is available for the stack and the heap. The stack is a
standard DOS defined and controlled area that can grow and
shrink as needed. Many books are available to define the
stack if you are interested in more information on it.
The heap is a Modula-2 entity that utilizes otherwise
unused memory to store data. It begins immediately
following the program and grows as necessary upward toward
the stack which is growing downward. As long as they never
meet, there is no problem. If they meet, a run-time error
is generated. The heap is therefore outside of the actual
program space.
If you did not understand the last two paragraphs, don't
worry. Simply remember that dynamically allocated variables
are stored on the heap and do not count in the 64K
limitation placed upon you by some compilers.
Back to our example program, POINTERS.MOD. When we
actually begin executing the program, we still have not
defined the variables we wish to use to store data in. The
first executable statement in line 15 generates a variable
for us with no name and stores it on the heap. Since it has
no name, we cannot do anything with it, except for the fact
that we do have a pointer "MyAge" that is pointing to it.
By using the pointer, we can store any INTEGER in it,
because that is its type, and later go back and retrieve it.
WHAT IS DYNAMIC ALLOCATION?
The variable we have just described is a dynamically
allocated variable because it was not defined in a VAR
declaration, but with an ALLOCATE procedure. The ALLOCATE
procedure creates a variable of the type defined by the
pointer, puts it on the heap, and finally assigns the
address of the variable to the pointer itself. Thus "MyAge"
contains the address of the variable generated. The
variable itself is referenced by using the pointer to it
followed by a ^, and is read, "the variable to which the
pointer points".
The ALLOCATE procedure requires 2 arguments, the first
of which is a pointer which will be used to point to the
desired new block of dynamically allocated menory, and the
second which gives the size of the block in bytes. The
supplied function TSIZE will return the size of the block of
data required by the TYPE supplied to it as an argument. Be
sure to use the TYPE of the data and not the TYPE of the
pointer to the data for the argument. Another procedure is
available named SIZE which returns the size of any variable
in bytes.
Page 73
CHAPTER 12 - Pointers and Dynamic Allocation
The next statement assigns a place on the heap to an
ARRAY type variable and puts its address in "MyName".
Following the ALLOCATE statements we have two assignment
statements in which the two variables pointed at are
assigned values compatible with their respective types, and
they are both written out to the video display. Notice that
both of these operations use the ^ which is the dereference
operator. By adding the dereference operator to the pointer
name, you can use the entire name just like any other
variable name.
The last two statements are illustrations of the way the
dynamically allocated variables are removed from use. When
they are no longer needed, they are disposed of with the
DEALLOCATE procedure, and the space on the heap is freed up
for use by other dynamically allocated variables.
In such a simple program, pointers cannot be
appreciated, but it is necessary for a simple illustration.
In a large, very active program, it is possible to define
many variables, dispose of some of them, define more, and
dispose of more, etc. Each time some variables are
deallocated, their space is then made available for
additional variables defined with the ALLOCATE procedure.
The heap can be made up of any assortment of variables,
they do not have to all be the same. One thing must be
remembered. Anytime a variable is defined, it will have a
pointer pointing to it. The pointer is the only means by
which the variable can be accessed. If the pointer to the
variable is lost or changed, the data itself is lost for all
practical purposes.
WHAT ABOUT THE "NEW" AND "DISPOSE" PROCEDURES?
The NEW and DISPOSE procedures are a carryover from
Pascal and are available on some Modula-2 compilers. When
they are available, they are simply translated internally
into calls to ALLOCATE and DEALLOCATE which must be imported
in order to use NEW and DISPOSE. Since they are being
removed from the language definition, their use should be
discouraged in favor of the more universal ALLOCATE and
DEALLOCATE procedures.
DYNAMICALLY STORING RECORDS;
The next example program, DYNREC.MOD, is a repeat of one
we studied in an earlier chapter. For your own edification,
review the example program BIGREC.MOD before going ahead in
this chapter.
Page 74
CHAPTER 12 - Pointers and Dynamic Allocation
Assuming that you are back in DYNREC.MOD, you will
notice that this program looks very similar to the earlier
one, and in fact they do exactly the same thing. The only
difference in the TYPE declaration is the addition of a
pointer "PersonID", and in the VAR declaration, the first
four variables are defined as pointers here, and were
defined as record variables in the last program.
WE JUST BROKE THE GREAT RULE OF MODULA-2
Notice in the TYPE declaration that we used the
identifier "Person" before we defined it, which is illegal
in Modula-2. Foreseeing the need to define a pointer prior
to the record, the designer of Modula-2 allows us to break
the rule in this one place. The pointer could have been
defined after the record in this case, but it was more
convenient to put it before, and in the next example
program, it will be required to put it before the record.
We will get there soon.
Examining the VAR declaration, we see that "Friend" is
really 50 pointers, so we have now defined 53 different
pointers to records, but no variables other than "Temp" and
"Index". We dynamically allocate a record with "Self"
pointing to it, and use the pointer to fill the new record.
Compare the statements that fill the record with the
corresponding statements in "BIGREC" and you will see that
they are identical except for the addition of the ^ to each
use of the pointer to designate the data pointed to.
THIS IS A TRICK, BE CAREFUL
Now go down to the place where "Mother" is assigned a
record and is then pointing to the record. It seems an easy
thing to do then to simply assign all of the values of
"Self" to all the values of "Mother" as shown in the next
statement, but it doesn't work. All the statement does, is
make the pointer "Mother" point to the same place where
"Self" is pointing. The data space that was allocated to
the pointer "Mother" is now somewhere on the heap, but since
we have lost the original pointer to it, we cannot find it,
use it, or deallocate it. This is an example of losing data
on the heap. The proper way is given in the next two
statements where all fields of "Father" are defined by all
fields of "Mother" which is pointing at the original "Self"
record. Note that since "Mother" and "Self" are both
pointing at the same record, changing the data with either
pointer results in the data appearing to be changed in both
because there is, in fact, only one data field.
Page 75
CHAPTER 12 - Pointers and Dynamic Allocation
A NOTE FOR PASCAL PROGRAMMERS
In order to WRITE from or READ into a dynamically
assigned record it is necessary to use a temporary record
since dynamically assigned records are not allowed to be
used in I/O statements in Pascal. This is not true in
Modula-2, and you can write directly out of a dynamically
allocated record in Modula-2. This is illustrated in the
section of the program that writes some data to the monitor.
Finally, the dynamically allocated variables are
deallocated prior to ending the program. For a simple
program such as this, it is not necessary to deallocate them
because all dynamic variables are deallocated automatically
when the program is terminated, but the DEALLOCATE steps are
included for illustration. Notice that if the
"DEALLOCATE(Mother)" statement was included in the program,
the data could not be found due to the lost pointer, and the
program would be unpredictable, probably leading to a system
crash.
SO WHAT GOOD IS THIS ANYWAY?
Remember when you were initially studying BIGREC? I
suggested that you see how big you could make the constant
"NumberOfFriends" before you ran out of memory. At that
time you probably found that it could be made slightly
greater than 1000 before you got the memory overflow message
at compilation. If your compiler uses a large memory model,
you may have been able to go much larger. Try the same
thing with DYNREC to see how many records it can handle,
remembering that the records are created dynamically, so you
will have to run the program to actually run out of memory.
The final result will depend on how much memory you have
installed, and how many memory resident programs you are
using such as "Sidekick". If you have a full memory of
640K, I would suggest you start somewhere around 8000
records of "Friend".
Now you should have a good idea of why Dynamic
Allocation can be used to greatly increase the usefulness of
your programs. There is, however, one more important topic
we must cover on dynamic allocation. That is the linked
list.
WHAT IS A LINKED LIST?
Understanding and using a linked list is by far the most
baffling topic you will confront in Modula-2. Many people
simply throw up their hands and never try to use a linked
list. I will try to help you understand it by use of an
Page 76
CHAPTER 12 - Pointers and Dynamic Allocation
example and lots of explanation. Examine the program
LINKLIST.MOD for an example of a linked list. I tried to
keep it short so you could see the entire operation and yet
do something meaningful.
To begin with, notice that there are two TYPEs defined,
a pointer to the record and the record itself. The record,
however, has one thing about it that is new to us, the last
entry, "Next" is a pointer to this very record. This record
then, has the ability to point to itself, which would be
trivial and meaningless, or to another record of the same
type which would be extremely useful in some cases. In
fact, this is the way a linked list is used. I must point
out, that the pointer to another record, in this case called
"Next", does not have to be last in the list, it can be
anywhere it is convenient for you.
A couple of pages ago, we discussed the fact that we had
to break the great rule of Modula-2 and use an identifier
before it was defined. This is the reason the exception to
the rule was allowed. Since the pointer points to the
record, and the record contains a reference to the pointer,
one has to be defined after being used, and by rules of
Modula-2, the pointer can be defined first. That is a
mouthful but if you just use the syntax shown in the
example, you will not get into trouble with it.
STILL NO VARIABLES?
It may seem strange, but we still will have no variables
defined, except for our old friend "Index". In fact for
this example, we will only define 3 pointers. In the last
example we defined 54 pointers, and had lots of storage
room. Before we are finished, we will have at least a dozen
pointers but they will be stored in our records, so they too
will be dynamically allocated.
Lets look at the program itself now. First, we create a
dynamically allocated record and define it by the pointer
"PlaceInList". It is composed of the three data fields, and
another pointer. We define "StartOfList" to point to the
first record created, and we will leave it unchanged
throughout the program. The pointer "StartOfList" will
always point to the first record in the linked list which we
are building up.
We define the three variables in the record to be any
name we desire for illustrative purposes, and set the
pointer in the record to NIL. NIL is a reserved word that
doesn't put an address in the pointer but defines it as
empty. A pointer that is currently NIL cannot be used to
Page 77
CHAPTER 12 - Pointers and Dynamic Allocation
write a value to the display as it has no value, but it can
be tested in a logical statement to see if it is NIL. It is
therefore a dummy assignment. With all of that, the first
record is completely defined.
DEFINING THE SECOND RECORD
When you were young you may have played a searching
game in which you were given a clue telling you where the
next clue was at. The next clue had a clue to the location
of the third clue. You kept going from clue to clue until
you found the prize. You simply exercised a linked list.
We will now build up the same kind of a list in which each
record will tell us where the next record is at.
We will now define the second record. Our goal will be
to store a pointer to the second record in the pointer field
of the first record. In order to keep track of the last
record, the one in which we need to update the pointer, we
will keep a pointer to it in "TempPlace". Now we can create
another new record and use "PlaceInList" to point to it.
Since "TempPlace" is still pointing at the first record, we
can use it to store the value of the pointer to the new
record in the old record. The 3 data fields of the new
record are assigned nonsense data for our illustration, and
the pointer field of the new record is assigned NIL.
Lets review our progress to this point. We now have the
first record with a name, composed of 3 parts, and a pointer
to the second record. We also have a second record storing a
different name and a pointer assigned NIL. We also have
three pointers, one pointing to the first record, one
pointing to the last record, and one we used just to get
here since it is only a temporary pointer. If you
understand what is happening so far, lets go on to add some
additional records to the list. If you are confused, go
back over this material again.
TEN MORE RECORDS
The next section of code is contained within a FOR loop
so the statements are simply repeated ten times. If you
observe carefully, you will notice that the statements are
identical to the second group of statements in the program
(except of course for the name assigned). They operate in
exactly the same manner, and we end up with ten more names
added to the list. You will now see why the temporary
pointer was necessary, but pointers are cheap, so feel free
to use them at will. A pointer only uses 4 bytes of memory.
We now have generated a linked list of twelve entries.
Page 78
CHAPTER 12 - Pointers and Dynamic Allocation
We have a pointer pointing at the first entry, and another
pointer pointing at the last. The only data stored within
the program itself are three pointers, and one integer, all
of the dynamically allocated data is on the heap. This is
one advantage to a linked list, it uses very little internal
memory, but it is costly in terms of programming. You
should never use a linked list simply to save memory, but
only because a certain program lends itself well to it.
Some sorting routines are extremely fast because of using a
linked list, and it could be advantageous to use in a
database.
A graphic picture of the data should aid in your
understanding of what we have done so far.
StartOfList-->FirstName (first Record)
Initial
LastName
Next---->FirstName (Second Record)
Initial
LastName
Next---->FirstName (Third Record)
Note; The pointer Initial
actually points to LastName
all 4 elements of Next----> etc.
the record. .
.
.
.
etc.--->FirstName (Record 11)
Initial
LastName
Next---->FirstName (Record 12)
Initial
PlaceInList------->LastName
Next---->NIL
HOW DO WE GET TO THE DATA NOW?
Since the data is in a list, how can we get a copy of
the fourth entry for example? The only way is to start at
the beginning of the list and successively examine pointers
until you reach the desired one. Suppose you are at the
fourth and then wish to examine the third. You cannot back
up, because you didn't define the list that way, you can
only start at the beginning and count to the third. You
could have defined the record with two pointers, one
pointing forward, and one pointing backward. This would be
a doubly-linked list and you could then go directly from
entry four to entry three.
Page 79
CHAPTER 12 - Pointers and Dynamic Allocation
Now that the list is defined, we will read the data from
the list and display it on the video monitor. We begin by
defining the pointer, "PlaceInList", as the start of the
list. Now you see why it was important to keep a copy of
where the list started. In the same manner as filling the
list, we go from record to record until we find the record
with NIL as a pointer.
Finally, it is necessary to DEALLOCATE the list, being
careful to check for the ending NIL before you deallocate
it. It will be left for you to DEALLOCATE the records if
you have such a desire.
There are entire books on how to use linked lists, and
many Modula-2 programmers will seldom, if ever, use them.
For this reason, additional detail is considered
unnecessary, but to be a fully informed Modula-2 programmer,
some insight into linked lists is necessary.
PROGRAMMING EXERCISE
1. Write a program to store a few names dynamically, then
display the stored names on the monitor. As your first
exercise in dynamic allocation, keep it very simple.
2. For a much more involved project, read in a list of
simple names and sort them alphabetically by searching
through the list to find where they should go. Insert
each new name into the list by changing pointer values.
For example, to add a new element between number 3 and
4, make the pointer in 3 point to the new element, and
make the pointer in the new element point to number 4.
It is important to note that adding data to the
beginning or end of the list must be handled as special
cases. This is definitely an advanced programming
exercise but you will be greatly rewarded for your
effort if you complete it.
Page 80